
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟列表实现</title>
   
</head>
<style type="text/css">
    *{
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        /* display: flex; */
    }
    body{
        display: flex;
        width: 100vw;
        height: 100vh;
        
    }
    #container{
        height: 400px;
        overflow: auto;
        margin: auto;
        width:200px;
    }
    #virtualList{
        display: grid;
        grid-template-columns: repeat(1, 1fr);
        grid-gap: 5px
    }
    .item{
        display: flex;
        align-items: center;
        justify-content: center;
        height: 50px;
        background-color: #f0f0f0;
        border-radius: 10px;
    }
    
</style>
<body>
    <div id="container">
        <!-- 虚拟列表 -->
        <div id="virtualList">
        </div>
        <div id="loading">加载更多....</div>
    </div>
</body>

<script type="text/javascript">
    //mock数据
    const data = Array.from({ length: 10000 }, (_, index) => `Item ${index + 1}`);

    //虚拟分页参数
    let startIndex = 0;
    let endIndex = 99;

    //获取容器
    const container = document.getElementById('container');

    //loading元素
    const loading = document.getElementById('loading');

    //加载标识
    let isLoading = false;

    //获取虚拟列表
    const virtualList = document.getElementById('virtualList');

    //添加子节点
    const addItems = (start, end) => {
        for (let i = start; i <= end; i++) {
            const div = document.createElement('div');
            div.className = 'item';
            div.appendChild(document.createTextNode(data[i]))
            virtualList.appendChild(div);
        }
    }

    //初始添加100个子节点
    addItems(startIndex, endIndex);


    //方法1

    //创建交叉观察器
    const observer = new IntersectionObserver((entries)=>{
        console.log('Observer callback', entries);
        entries.forEach((entry)=>{
            if(entry.isIntersecting){
                //到达可视区域,执行数据加载
                startIndex +=100
                endIndex += 100;
                addItems(startIndex,endIndex)

            }
        })
    },{threshold:1});

    //监听loading元素是否进入视口区域
    if(loading){
        observer.observe(loading);
    }

    // 方法2

    //监听滚动事件
    container.addEventListener('scroll',(event)=>{
        // console.log('event',event);
        // 计算可视区域的高度
        const scrollHeight = container.scrollHeight;
        const clientHeight = container.clientHeight;
        const scrollTop = container.scrollTop;

        //计算触底
        const isBottom = scrollTop + clientHeight >= scrollHeight;

        // 触底并且没有在执行数据加载操作
        if(isBottom &&!isLoading){
            // 开始执行数据加载操作
            isLoading = true;
            // 模拟数据加载
            setTimeout(()=>{
                // 更新虚拟分页参数
                startIndex +=100
                endIndex += 100;
                 addItems(startIndex, endIndex);
                // 数据加载完成
                isLoading = false;
            },1000)
        }
    }) 

    //将监听卸载
    document.onunload = function () {
        observer.disconnect();
        container.removeEventListener('scroll')
    }

    //方法1跟方法2的优缺点
    // 因为 IntersectionObserver 在 safari 和 IE 11 下不兼容，
    // 并且 IntersectionObserver 也存在一些 bug，
    // 所以在不兼容的浏览器中，可以使用 scroll 事件来代替 IntersectionObserver
    // 但是 scroll 事件在滚动过程中会触发多次(可以使用节流处理)，造成重复计算问题，在性能上是浪费的情况
    // 所以，在性能上，IntersectionObserver 是更好的选择
    // IntersectionObserver 还可以监听多个元素，并可以对元素的相交状态进行监听，
    // 这在需要对多个元素进行同一操作时非常有用
    // 但是，IntersectionObserver 的兼容性也不友好，
    // 所以，在兼容性上，scroll 事件是更好的选择
    // 所以，在需要对多个元素进行同一操作时，IntersectionObserver 是一个更好的选择
    // 但是，在兼容性上，scroll 事件是一个更好的选择
    // 在性能上，IntersectionObserver 是更好的选择






</script>
</html>
